package com.shou.lifecollege.tool.redis.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Redis系列化等配置类：
 * 主要有对spring cache 的配置 和 使用fastjson系列化的配置
 * @Author: luotian
 * @Date: 2022/11/22 15:55
 */
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {

    /**
     *  设置spring cache: 设置@Cacheable 序列化方式 和 设置 spring cache 数据默认过期时间，默认2小时；解决了redis中显示乱码的问题；
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        // 使用 fastjson替换默认系列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(
                        // 设置使用 fastjson系列化
                        RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                //设置为2小时redis值过期
                .entryTtl(Duration.ofHours(2));
        return configuration;
    }

    /**
     *  设置spring cache: 注解@cacheable 重写 redis cache异常，只打印日志，当redis失败时，会重新请求应用
     *
     * @return
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {

        // 异常处理，当redis异常时，只打印日志，但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, exception);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}]", key, value, exception);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key ->[{}]", key, exception);

            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("Redis occur handleCacheClearError:", exception);

            }
        };
    }



    /**
     *  设置spring cache: 注解 @Cacheable 的 redisKey 的自定义生成器，缓存时，会用这个生成key
     * 用法 ：@Cacheable(cacheNames = "redisUser_cache", keyGenerator = "keyGenerator")
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {

        return (target, method, params) -> {
            Map<String, Object> container = new HashMap<>(8);
            Class<?> targetClass = target.getClass();
            // 包名称
            container.put("package", targetClass.getPackage());
            // 类地址
            container.put("class", targetClass.toGenericString());
            // 方法名
            container.put("methodName", method.getName());
            // 参数列表
            for (int i = 0; i < params.length; i++) {
                container.put(String.valueOf(i), params[i]);
            }
            // 转为JSON字符串
            String jsonString = JSON.toJSONString(container);
            // 做 SHA256 Hash计算，得到SHA256值作为Key
            return DigestUtils.sha256Hex(jsonString);
        };
    }

    /**
     * RedisTemplate 的注入配置
     * @param redisConnectionFactory
     * @return
     */
    @SuppressWarnings("all")
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 序列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);

        // key的序列化,使用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);

        // fastjson 升级到 1.2.83 后需要指定序列化白名单.不然会报错：
        // com.alibaba.fastjson.JSONException: autoType is not support. space.goldchen.pojo.RedisUser
        ParserConfig.getGlobalInstance().addAccept("space.goldchen");
        return template;
    }

    /**
     * 重写序列化器： fastjson对redis中value的序列化
     */
    class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
        private final Class<T> clazz;

        FastJsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }

        @Override
        public byte[] serialize(T t) {
            if (t == null) {
                return new byte[0];
            }
            // 加上SerializerFeature.WriteClassName后，生成的json中会有对象的标记：
            // 如：@class:"space.goldchen.pojo.RedisUser"
            return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(StandardCharsets.UTF_8);
        }

        @Override
        public T deserialize(byte[] bytes) {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, StandardCharsets.UTF_8);
            return JSON.parseObject(str, clazz);
        }
    }

    /**
     * 重写序列化器： fastjson对redis中key的序列化
     */
    class StringRedisSerializer implements RedisSerializer<Object> {
        private final Charset charset;

        private StringRedisSerializer(Charset charset) {
            Assert.notNull(charset, "Charset must not be null!");
            this.charset = charset;
        }

        StringRedisSerializer() {
            this(StandardCharsets.UTF_8);
        }

        @Override
        public byte[] serialize(Object o) {
            String string = JSON.toJSONString(o);
            if (org.apache.commons.lang3.StringUtils.isBlank(string)) {
                return null;
            }
            string = string.replace("\"", "");
            return string.getBytes(charset);
        }

        @Override
        public Object deserialize(byte[] bytes) {
            return (bytes == null ? null : new String(bytes, charset));
        }
    }

}

